iT邦幫忙

2025 iThome 鐵人賽

DAY 22
0
Modern Web

Web Bluetooth API 實戰:30 天打造通用 BLE 偵錯工具系列 第 22

Day 22:專案核心 (4):`getCharacteristics()` 動態探索所有特徵

  • 分享至 

  • xImage
  •  

昨天,我們使用 server.getPrimaryServices() 這張「全景地圖」,我們成功地找到了島上所有隱藏的洞穴(服務),並在我們的 UI 上為它們一一建立了標記。我們的網頁不再是空白一片,而是動態地展示了裝置的宏觀結構。

但是,洞穴的入口並不是我們的終點。我們真正的目標,是洞穴深處閃閃發光的寶藏——那些真正承載著數據、等待我們讀取和寫入的「特徵 (Characteristics)」。心率數值、電池電量、溫度讀數... 所有這些有價值的資訊,都存放在特徵之中。

今天,我們的任務就是深入每一個已發現的服務洞穴,點亮火把,照亮其中的每一條通道,找到所有隱藏的寶箱。我們將使用的關鍵指令是 service.getCharacteristics()。這一步,是我們從「宏觀探索」轉向「微觀尋寶」的決定性一步,也是我們能與裝置進行實質性數據交換前的最後一道關卡。


1. 尋寶者的放大鏡: service.getCharacteristics()

這是我們深入服務內部,尋找寶藏的關鍵工具。

  • 隸屬BluetoothGATTService 物件的方法。也就是我們昨天在 for...of 迴圈中得到的那個 service 物件。

  • 非同步:它同樣是一個非同步操作,會回傳一個 Promise

  • 回傳值:當 Promise 成功解析時,它會回傳一個陣列。這個陣列中,包含了該服務下所有特徵的 BluetoothGATTCharacteristic 物件。

核心中的核心:characteristic.properties

API 回傳的每一個 characteristic 物件,都附帶一個名為 properties 的屬性。這個屬性本身也是一個物件,它像一本「特徵能力說明書」,用布林值(true/false)告訴我們這個特徵到底能做什麼。

// 一個 characteristic.properties 物件的範例
{
  read: true,          // 這個特徵的值可以被讀取
  write: false,        // 不可寫入
  notify: true,        // 可以被訂閱 (當值改變時會主動通知我們)
  indicate: false,     // 另一種通知形式
  writeWithoutResponse: false // 無需回應的寫入
  // ... 等等
}

這個 properties 物件,正是我們 Day 13/14 打造的 renderCharacteristic 函式所需要的最關鍵的「原料」!


2. 程式碼實戰:深入洞穴,尋找寶藏

我們的策略是,在昨天遍歷「服務」的 for...of 迴圈內部,再嵌套一個新的迴圈來遍歷「特徵」。

打開 app.js,找到昨天的 for (const service of services) 迴圈,並在其中加入探索特徵的邏輯:

// app.js -> scanButton.onclick -> for...of 迴圈內部

for (const service of services) {
  log(`>> 正在處理服務: ${service.uuid}`);
  // ... (昨天儲存 service 和創建 serviceCard 的程式碼保持不變) ...
  const serviceCard = gattProfile.services[service.uuid].uiCard;

  // ---- ↓↓↓ 今天新增的核心程式碼 ↓↓↓ ----

  try {
    log(`---> 正在探索特徵...`);
    const characteristics = await service.getCharacteristics();
    log(`---> 發現 ${characteristics.length} 個特徵!`);

    // 再次使用 for...of 迴圈,遍歷這個服務下的所有特徵
    for (const characteristic of characteristics) {
      log(`-----> 特徵 UUID: ${characteristic.uuid}`);

      // 步驟 1: 將特徵資訊完整地存入我們的資料模型
      gattProfile.services[service.uuid].characteristics[characteristic.uuid] = {
        uuid: characteristic.uuid,
        properties: characteristic.properties, // 儲存最關鍵的屬性物件
        instance: characteristic,             // 保存原始實例
        value: null                           // 準備好存放數值的空間
      };

      // 步驟 2: 將真實的特徵資訊,餵給我們最強大的 UI 工廠函式!
      renderCharacteristic(
        { // 傳入我們需要的資訊
          uuid: characteristic.uuid,
          properties: characteristic.properties
        },
        serviceCard // 告訴函式要把 UI 渲染到哪張服務卡片上
      );
    }
  } catch(error) {
    log(`-----> 探索特徵失敗: ${error.message}`);
  }
  // ---- ↑↑↑ 今天新增的核心程式碼 ↑↑↑ ----
}

程式碼解析

  1. 嵌套迴圈:我們在處理單一 service 的迴圈內部,呼叫了 service.getCharacteristics(),然後再用一個新的 for...of 迴圈來處理返回的 characteristics 陣列。這形成了一個完美的「服務 -> 特徵」的探索結構。

  2. 完整的資料模型:我們現在將 characteristic.uuidcharacteristic.propertiescharacteristic 的原始實例,全部存入了 gattProfile 中對應的服務底下。至此,我們的 gattProfile 物件已經完整地映射了真實裝置的 GATT 結構!

  3. renderCharacteristic 的威力:還記得我們在 Day 14 精心打造的那個函式嗎?現在是它大顯神威的時候了!我們將從真實裝置獲取到的 uuidproperties 傳遞給它。它會自動判斷:如果 properties.readtrue,就生成一個「Read」按鈕;如果 properties.writetrue,就生成一個輸入框和「Write」按鈕...

現在,重新整理頁面,連接到你的藍牙裝置。你會看到,網頁上不僅動態生成了服務卡片,每個服務卡片下面,還根據該服務真實擁有的特徵,動態地生成了對應的、功能完備的互動面板!


總結與後續

今天,我們完成了尋寶之旅中最關鍵、也是最後的探索步驟!
至此,我們應用程式的「探索」階段已完美收官。我們能根據根據目標裝置的特性,自動準備就緒。
我們已經找到了所有的寶箱,並且知道了每個寶箱上面是掛著「讀取鎖」還是「寫入鎖」。從明天開始,我們將學習如何打開這些鎖。我們將從最直接的操作——讀取 (Read)——開始,學習 characteristic.readValue(),並第一次從裝置中,將真實的數據讀取到我們的網頁上!

那麼今天的內容就到這邊,感謝你能看到這裡,在這邊祝你早安、午安、晚安,我們明天見。


上一篇
Day 21 專案核心 (3):`getPrimaryServices()` 動態探索所有服務
下一篇
Day 23: 整合與渲染:將探索結果動態生成 UI
系列文
Web Bluetooth API 實戰:30 天打造通用 BLE 偵錯工具24
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言